﻿Imports System.Threading
Imports System.Net
Imports System.Net.Sockets
Imports System.IO

Module InternetInnovationEngine
    Dim WorkerClass As iieMainClass
    Dim cki As ConsoleKeyInfo
    Dim OnConnectAction As String = String.Empty
    Dim OnDisconnectAction As String = String.Empty
    Dim OnReceiveAction As String = String.Empty
    Dim TempFolder As String = String.Empty
    Dim DebugFolder As String = String.Empty
    Dim ListenAddress As String = String.Empty
    Dim ListenPort As String = String.Empty
    Dim TimerValue As String = "500"
    Dim MaxThreads As String = "10"
    Dim ErrMessage As String = String.Empty
    Dim RequiredStringToSendPackets As String = String.Empty
    Dim ExcludeRequiredPacketsFromTransmission As Boolean = False
    Dim ConnectMessage As String = String.Empty
    Dim NeverTreatAsFileNames As Boolean = False
    Dim IsSSL As Boolean
    Dim AlwaysPassClientInputAsParameters As Boolean = False
    Dim AutoCloseConnection As Boolean = False
    Dim DoNotCloseConnectionOnExternalError As Boolean = False
    Dim SSLCertificateFile As String = String.Empty

    Public Sub DisplayUsage()
        Console.WriteLine("Usage:")
        Console.WriteLine("Items in square brackets are optional.  Do not actually type the square brackets...  The paramter delimiter is a SPACE followed by two DASHES.")
        Console.WriteLine("iie [ --help][ --ac][ --mt=MaxWorkThreads][ --d=debugfolder][ --dncof][ --rsts=ReqdString][ --ers][ --errmsg=Message][ --la=IP][ --ito=Timeout][ --ssl=certfile][ --C=command][ --D=command][ --atap][ --atac] --R=command --lp=port --t=tmpfolder")
        Console.WriteLine()
        Console.WriteLine("Explanation of parameters")
        Console.WriteLine()
        Console.WriteLine(" --help displays this screen.")
        Console.WriteLine()
        Console.WriteLine(" --d signals the use of debug mode where all input and output files are copied to the specified folder.")
        Console.WriteLine()
        Console.WriteLine(" --la sets the local address that the server will listen on.  If ignored, the system picks the first one detected.")
        Console.WriteLine()
        Console.WriteLine(" --lp sets the local port that the server will listen on.")
        Console.WriteLine()
        Console.WriteLine(" --mt sets the maximum number of concurrent working threads allowed at one time.  The default is 10 threads.")
        Console.WriteLine()
        Console.WriteLine(" --ito sets the idle time out value for the packet accumulator.  When this delay time has elapsed, any data accumulated in the packet buffer is purged.  Setting this to zero causes the system to send all packets as received.  This defaults to 500 milliseconds.")
        Console.WriteLine()
        Console.WriteLine(" --ssl sets the path and file name of an ssl certificate that the server will use for transport security.")
        Console.WriteLine()
        Console.WriteLine(" --C sets the path and file name of the command to run when an IP and source port connects.  This information is passed as the only parameter to the command in the form IP:Port (i.e. 127.0.0.1:53242)")
        Console.WriteLine()
        Console.WriteLine(" --D sets the path and file name of the command to run when an IP and source port disconnects.  This information is passed as the only parameter to the command in the form IP:Port (i.e. 127.0.0.1:53242)")
        Console.WriteLine()
        Console.WriteLine(" --R sets the path and file name of the command to run when a connected socket receives data.  The only parameter passed to the command is the filename that contains the original request.")
        Console.WriteLine()
        Console.WriteLine(" --t sets the temp path where all input and output files are stored.")
        Console.WriteLine()
        Console.WriteLine(" --dncof signals iie to not close client connections when external processes return a failure code.")
        Console.WriteLine()
        Console.WriteLine(" --conmsg sends a specified message back to the client when the client initially connects to the server.")
        Console.WriteLine()
        Console.WriteLine(" --errmsg sends a specified message back to the client if the external process ends in error.")
        Console.WriteLine()
        Console.WriteLine(" --ac signals iie to automatically close client connections when external processes return and the data has been returned to the client.")
        Console.WriteLine()
        Console.WriteLine(" --atac means Always Treat As Content.  In other words, if a file name is passed back from a receive command, it will be passed back to the client as a literal not as the contents of the file.")
        Console.WriteLine()
        Console.WriteLine(" --atap means Always Treat As Parameters.  In other words, All content received from the client is passed directly to the command as command line parameters instead of being passed within a file via filename.")
        Console.WriteLine()
        Console.WriteLine(" --rsts means Require String To Send.  In other words, All content received from the client is not passed until the string specified is found within the input.")
        Console.WriteLine()
        Console.WriteLine(" --ers means Exclude Required String.  In other words, if the rsts parameter is used, remove the string from the output buffer prior to sending it.")
        Console.WriteLine()
        Console.WriteLine()
        Console.WriteLine("Examples:")
        Console.WriteLine()
        Console.WriteLine("Windows users...")
        Console.WriteLine()
        Console.WriteLine("iie --d=c:\temp\debug --la=192.168.0.12 --lp=8080 --ito=250 --ssl=c:\cert folder\mycert.cer --C=c:\my programs\logconnection.bat --R=c:\my programs\processdata.exe --t=c:\temp")
        Console.WriteLine()
        Console.WriteLine("Linux users... (mono required...[mono-complete and mono-vbnc])")
        Console.WriteLine()
        Console.WriteLine("The following just echoes the request back to the client.  You can use telnet to test.  When I tested I had to hit enter to commit the transmission (linux).")
        Console.WriteLine("mono iie.exe --d=/home/mniles/logs --la=192.168.0.12 --lp=8080 --ito=250 --ssl=/home/mniles/mycert.cer --R=/bin/cat --t=/home/mniles/temp")
        Console.WriteLine()
        Console.WriteLine("Enjoy!")
    End Sub

    Public Sub Main(ByVal sArgs() As String)       
        Dim arrArgs() As String = Split(Command.ToString()," --")
        Dim arrParameter() As String
        Dim i As Integer
        Console.WriteLine("The Internet Innovation Engine AKA iie")
        Console.WriteLine("Created by Michael Niles")
        If arrArgs(0) <> Nothing Then
            Try

                For i = LBound(arrArgs) To UBound(arrArgs)
                    arrArgs(i) = arrArgs(i).Replace("--", String.Empty)
                    arrParameter = Split(arrArgs(i), "=")
                    If arrParameter(0) = "d" Then
                        DebugFolder = arrParameter(1)
                    ElseIf arrParameter(0) = "help" Then
                        DisplayUsage()
                        End
                    ElseIf arrParameter(0) = "t" Then
                        TempFolder = arrParameter(1)
                    ElseIf arrParameter(0) = "ito" Then
                        TimerValue = arrParameter(1)
                    ElseIf arrParameter(0) = "dncof" Then
                        DoNotCloseConnectionOnExternalError = True
                    ElseIf arrParameter(0) = "rsts" Then
                        RequiredStringToSendPackets = arrParameter(1)
                    ElseIf arrParameter(0) = "atac" Then
                        NeverTreatAsFileNames = True
                    ElseIf arrParameter(0) = "ers" Then
                        ExcludeRequiredPacketsFromTransmission = True
                    ElseIf arrParameter(0) = "atap" Then
                        AlwaysPassClientInputAsParameters = True
                    ElseIf arrParameter(0) = "ac" Then
                        AutoCloseConnection = True
                    ElseIf arrParameter(0) = "la" Then
                        ListenAddress = arrParameter(1)
                    ElseIf arrParameter(0) = "lp" Then
                        ListenPort = arrParameter(1)
                        ListenPort = ListenPort.Replace("-", String.Empty)
                    ElseIf arrParameter(0) = "mt" Then
                        MaxThreads = arrParameter(1)
                    ElseIf arrParameter(0) = "conmsg" Then
                        ConnectMessage = arrParameter(1).Replace("""", String.Empty).Replace("\r", vbCr).Replace("\n", vbLf)
                    ElseIf arrParameter(0) = "errmsg" Then
                        ErrMessage = arrParameter(1).Replace("""", String.Empty).Replace("\r", vbCr).Replace("\n", vbLf)
                    ElseIf arrParameter(0) = "ssl" Then
                        SSLCertificateFile = arrParameter(1)
                    ElseIf arrParameter(0) = "C" Then
                        OnConnectAction = arrParameter(1)
                    ElseIf arrParameter(0) = "D" Then
                        OnDisconnectAction = arrParameter(1)
                    ElseIf arrParameter(0) = "R" Then
                        OnReceiveAction = arrParameter(1)
                    Else
                        If Not (arrParameter(0).Contains("iie.exe")) Then
                            Console.WriteLine("Unknown parameter " & arrParameter(0))
                            Console.Write("use --help to display options")
                            Console.Write(vbNewLine)
                        End If
                    End If
                Next
            Catch ex As Exception
                Console.WriteLine("Error parsing the parameters")
                End
            End Try

        Else
            DisplayUsage()
            End
        End If
        If (OnReceiveAction.Trim = String.Empty) Or (TempFolder.Trim = String.Empty) Or (ListenPort.Trim = String.Empty) Then
            Console.Write("At least one required parameter was omitted")
            Console.Write(vbNewLine)
            Console.Write("use --help to display options")
            Console.Write(vbNewLine)
            End
        End If
        If Not (Int16.TryParse(TimerValue, Nothing)) Then
            Console.Write("The specified millisecond transmission timeout value appears to be invalid.  Please ensure it is numeric and falls within the bounds. This defaults to 500 milliseconds.")
            Console.Write(vbNewLine)
            'DisplayUsage()
            End
        End If
        If Not (Int16.TryParse(ListenPort, Nothing)) Then
            Console.Write("The specified listen port appears to be invalid.  Please ensure it is numeric and falls within the bounds 1-65534")
            Console.Write(vbNewLine)
            End
        End If
        If Not (Int16.TryParse(MaxThreads, Nothing)) Then
            Console.Write("The specified maximum worker threads appears to be invalid.  Please ensure it is numeric and falls within the bounds 1-65534")
            Console.Write(vbNewLine)
            End
        End If
        If Not (File.Exists(OnReceiveAction)) Then
            Console.Write("The receive action command file does not appear to exist.  Please validate the file name and file permissions of the user running iie...")
            Console.Write(vbNewLine)
            End
        End If
        If (Not (OnConnectAction.Trim = String.Empty)) And (Not (File.Exists(OnConnectAction))) Then
            Console.Write("The specified connect action command file does not appear to exist.  Please validate the file name and file permissions of the user running iie...")
            Console.Write(vbNewLine)
            End
        End If
        If (Not (OnDisconnectAction.Trim = String.Empty)) And (Not (File.Exists(OnDisconnectAction))) Then
            Console.Write("The specified disconnect action command file does not appear to exist.  Please validate the file name and file permissions of the user running iie...")
            Console.Write(vbNewLine)
            End
        End If
        AddHandler Console.CancelKeyPress, AddressOf myHandler
        Console.WriteLine("Starting Server...")
        WorkerClass = New iieMainClass(DebugFolder, TempFolder, ListenAddress, ListenPort, SSLCertificateFile, OnConnectAction, OnDisconnectAction, OnReceiveAction, TimerValue, NeverTreatAsFileNames, AlwaysPassClientInputAsParameters, AutoCloseConnection, DoNotCloseConnectionOnExternalError, ErrMessage, ConnectMessage, RequiredStringToSendPackets, ExcludeRequiredPacketsFromTransmission, Int16.Parse(MaxThreads))
        If (WorkerClass Is Nothing) Then
            Console.WriteLine("Server start failed!  Check your parameters!")
            Console.Write(vbNewLine)
            End
        End If
        If Not (WorkerClass.SuccessfulListen) Then
            Console.WriteLine("Server start failed!  Check your parameters!")
            Console.Write(vbNewLine)
            End
        End If
        Console.WriteLine("Server is now running on " & WorkerClass.GetLocalIP & ":" & WorkerClass.ListenPort)
        Console.WriteLine("press the 'X' key to stop the server and exit")
        While True
            ' Start a console read operation. Do not display the input.
            cki = Console.ReadKey(True)
            ' Exit if the user pressed the 'X' key. 
            If cki.Key = ConsoleKey.X Then Exit While
        End While
        Console.WriteLine("Server is stopping.")
        WorkerClass.Exit_Server()
        WorkerClass = Nothing
    End Sub
    Public Sub myHandler(ByVal sender As Object, _
                                   ByVal args As ConsoleCancelEventArgs)
        Try
            Console.WriteLine("Server is stopping.")
            WorkerClass.Exit_Server()
        Catch ex As Exception

        End Try
        WorkerClass = Nothing
        args.Cancel = True
        End
    End Sub
End Module
Public Class iieMainClass

    Public WithEvents SocketConnected As TCPClientSocketClient      ' Events that are fired from within the threads of the TCPClientSocketClient 
    Public WithEvents SocketDisconnected As TCPClientSocketClient   ' Class
    Public WithEvents DataIn As TCPClientSocketClient               ' They are, all three, self descriptive 
    Public LocalServer As TCPServerListener
    Private AllTCPClientProcesses As New Hashtable
    Dim BeginServer As Thread
    Dim OnConnectAction As String = String.Empty
    Dim OnDisconnectAction As String = String.Empty
    Dim OnReceiveAction As String = String.Empty
    Dim TempFolder As String = String.Empty
    Dim DebugFolder As String = String.Empty
    Dim TimeOutValue As Integer = 500
    Dim NeverTreatAsFileNames As Boolean = False
    Dim AlwaysPassClientInputAsParameters As Boolean = False
    Dim AutoCloseConnection As Boolean = False
    Dim DoNotCloseConnectionOnExternalProcFailure As Boolean = False
    Dim ExcludeRequiredPacketsFromTransmission As Boolean = False
    Dim EchoErrorMessageOnFail As String = String.Empty
    Dim StringRequiredToSendPackets As String = String.Empty
    Dim ConnectMessage As String = String.Empty
    Public ListenAddress As String = String.Empty
    Public ListenPort As String = String.Empty
    Dim IsSSL As Boolean = False
    Dim SSLCertificateFile As String = String.Empty
    Dim syncfield As Object = New Object
    Dim MaxWorkerThreads As Integer = 10
    Public SuccessfulListen As Boolean = False

    Public Sub New(ByVal debugmode As String, ByVal exttempfolder As String, ByVal localaddress As String, ByVal localport As String, ByVal sslpath As String, ByVal connectcommand As String, ByVal disconnectcommand As String, ByVal receivecommand As String, ByVal timervalue As String, ByVal neverreturnsfilenames As Boolean, ByVal alwayspasscontentasparameters As Boolean, ByVal autocloseconn As Boolean, ByVal notcloseonfail As Boolean, ByVal errmsg As String, ByVal conmsg As String, ByVal reqdstring As String, excludereqdstring As Boolean, ByVal maxthreads As Integer)
        StringRequiredToSendPackets = reqdstring
        ExcludeRequiredPacketsFromTransmission = excludereqdstring
        AlwaysPassClientInputAsParameters = alwayspasscontentasparameters
        AutoCloseConnection = autocloseconn
        ConnectMessage = conmsg
        DebugFolder = debugmode
        DoNotCloseConnectionOnExternalProcFailure = notcloseonfail
        TempFolder = exttempfolder
        ListenAddress = localaddress
        ListenPort = localport
        EchoErrorMessageOnFail = errmsg
        NeverTreatAsFileNames = neverreturnsfilenames
        MaxWorkerThreads = maxthreads
        TimeOutValue = Int16.Parse(timervalue)
        If sslpath <> String.Empty Then
            If File.Exists(sslpath) Then
                IsSSL = True
                SSLCertificateFile = sslpath
            End If
        End If

        OnConnectAction = connectcommand
        OnDisconnectAction = disconnectcommand
        OnReceiveAction = receivecommand

        AddHandler TCPClientSocketClient.NewIncomingConnection, AddressOf whoConnected_changed
        AddHandler TCPClientSocketClient.ExistingConnectionClosed, AddressOf whoDisconnected_changed
        AddHandler TCPClientSocketClient.NewDataArrived, AddressOf ProcessNewData

        Try
            BeginServer = New Thread(AddressOf ServerBegin)
            BeginServer.Start()
        Catch ex As Exception
            SuccessfulListen = False
        End Try
        SuccessfulListen = True

    End Sub
    Private Function GetIPByProcessID(ByVal ProcessID As String) As String
        GetIPByProcessID = AllTCPClientProcesses.Item(ProcessID)
    End Function
    Private Function GetClientByIP(ByVal whoConnected As String) As TCPClientSocketClient
        GetClientByIP = TCPClientSocketClient.AllTCPClientSockets.Item(whoConnected)
    End Function
    Private Function SendMessageToClient(ByVal whoConnected As String, ByVal Message As Byte()) As Boolean
        Try
            Return GetClientByIP(whoConnected).SendMessage(Message)
        Catch ex As Exception
            Try
                GetClientByIP(whoConnected).Disconnect()
            Catch ex2 As Exception

            End Try

            Return False
        End Try

    End Function
    Private Function StreamFileToClient(ByVal whoConnected As String, ByVal FileName As String) As Boolean
        Try
            Return GetClientByIP(whoConnected).StreamFile(FileName)
        Catch ex As Exception
            Try
                GetClientByIP(whoConnected).Disconnect()
            Catch ex2 As Exception
                'Swallow exception
            End Try

            System.IO.File.Delete(FileName)
            Return False
        End Try

    End Function

    Private Sub DisconnectClient(ByVal whoConnected As String)
        Try
            If Not (GetClientByIP(whoConnected) Is Nothing) Then
                GetClientByIP(whoConnected).Disconnect()
            End If

        Catch ex As Exception

        End Try
    End Sub
    Private Sub whoConnected_changed(ByVal whoConnected As String) Handles SocketConnected.NewIncomingConnection
        Log(whoConnected & " > Connected")
        Try
            If ConnectMessage <> String.Empty Then
                If File.Exists(ConnectMessage) Then
                    'EchoErrorMessageOnFail is a file
                    If Not (StreamFileToClient(whoConnected, ConnectMessage)) Then
                    End If
                Else
                    'EchoErrorMessageOnFail is a literal
                    If Not SendMessageToClient(whoConnected, StrToByteArray(ConnectMessage)) Then
                    End If
                End If

            End If

            If OnConnectAction.Trim <> String.Empty Then
                If System.IO.File.Exists(OnConnectAction) Then
                    Dim MyProcess As New System.Diagnostics.Process
                    Dim OutputStr As String = String.Empty
                    Dim ErrorStr As String = String.Empty
                    MyProcess.StartInfo.CreateNoWindow = True
                    MyProcess.StartInfo.RedirectStandardOutput = True
                    MyProcess.StartInfo.RedirectStandardError = True
                    MyProcess.StartInfo.FileName = OnConnectAction
                    MyProcess.StartInfo.Arguments = whoConnected
                    MyProcess.StartInfo.UseShellExecute = False
                    MyProcess.Start()
                    Try
                        OutputStr = MyProcess.StandardOutput.ReadToEnd
                    Catch ex As Exception

                    End Try
                    Try
                        ErrorStr = MyProcess.StandardError.ReadToEnd
                    Catch ex As Exception

                    End Try
                    MyProcess.WaitForExit()

                    Log("Sending to client[" & OutputStr & "]")
                    SendMessageToClient(whoConnected, StrToByteArray(OutputStr))
                End If
            End If
        Catch ex As Exception
            'Swallow exception
        End Try
    End Sub

    Private Sub whoDisconnected_changed(ByVal whoDisconnected As String) Handles SocketDisconnected.ExistingConnectionClosed ' Actions when events happen
        Log(whoDisconnected & " > Disconnected")
        Try
            If OnDisconnectAction.ToString.Trim <> String.Empty Then
                If System.IO.File.Exists(OnDisconnectAction) Then
                    Dim MyProcess As New System.Diagnostics.Process
                    Dim OutputStr As String = String.Empty
                    Dim ErrorStr As String = String.Empty
                    MyProcess.StartInfo.CreateNoWindow = True
                    MyProcess.StartInfo.RedirectStandardOutput = True
                    MyProcess.StartInfo.RedirectStandardError = True
                    MyProcess.StartInfo.FileName = OnDisconnectAction
                    MyProcess.StartInfo.Arguments = whoDisconnected
                    MyProcess.StartInfo.UseShellExecute = False
                    MyProcess.Start()
                    Try
                        OutputStr = MyProcess.StandardOutput.ReadToEnd()
                    Catch ex As Exception

                    End Try
                    Try
                        ErrorStr = MyProcess.StandardError.ReadToEnd
                    Catch ex As Exception

                    End Try

                    MyProcess.WaitForExit()
                End If
            End If
        Catch ex As Exception
            ' Swallow exception
        End Try
    End Sub
    Private Function SaveToFile(msg As Byte(), filepath As String) As Boolean
        Dim sw As System.IO.FileStream = Nothing
        Try
            SaveToFile = False
            If Not IsFileLocked(filepath) Then
                If System.IO.File.Exists(filepath) Then
                    File.Delete(filepath)
                End If

                sw = New FileStream(filepath, FileMode.Create, FileAccess.Write)
                sw.Write(msg, 0, msg.Length)
                sw.Close()
                SaveToFile = True
            End If
        Catch
            SaveToFile = False
        End Try
    End Function


    Private Function IsFileLocked(filename As String) As Boolean
        Dim stream As FileStream = Nothing
        Dim file As New FileInfo(filename)
        Try
            If System.IO.File.Exists(filename) Then
                stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)
            End If
        Catch generatedExceptionName As IOException
            'the file is unavailable because it is: 
            'still being written to 
            'or being processed by another thread 
            'or does not exist (has already been processed) 
            Return True
        Finally
            If stream IsNot Nothing Then
                stream.Close()
            End If
        End Try
        Return False
    End Function
    Private Function ByteArrayToStr(Bytes As Byte()) As String
        ByteArrayToStr = System.Text.Encoding.ASCII.GetString(Bytes)
    End Function
    Private Function StrToByteArray(str As String) As Byte()
        Dim encoding As New System.Text.UTF8Encoding()
        Return encoding.GetBytes(str)
    End Function
    Private Sub datain_ProcessExited(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim IsFile As Boolean = False
        Dim tmpFileName As String = DirectCast(sender, System.Diagnostics.Process).StartInfo.Arguments.Replace("""", String.Empty)
        Dim ClientIP As String = GetIPByProcessID(sender.Id.ToString)
        If ClientIP.IndexOf(".[") > -1 Then
            ClientIP = Mid(ClientIP, 1, ClientIP.IndexOf(".["))
        End If
        Try
            AllTCPClientProcesses.Remove(sender.Id.ToString)
        Catch ex As Exception

        End Try
        Dim OutputStr As String = sender.StandardOutput.ReadToEnd
        If File.Exists(OutputStr) Then
            IsFile = True
        End If
        If NeverTreatAsFileNames = True Then
            IsFile = False
        End If
        If Not IsFile Then
            If OutputStr.Trim <> String.Empty Then
                Try
                    If DebugFolder <> String.Empty Then
                        Dim tmpOutFileName As String = sender.StartInfo.Arguments.Replace(TempFolder, DebugFolder).Replace(".request", ".responseconsole")
                        If Not (Directory.Exists(DebugFolder)) Then
                            Directory.CreateDirectory(DebugFolder)
                        End If
                        Log(ClientIP & " < Copying output from process to file [" + tmpOutFileName + "] and placing in debug folder")
                        If Not (SaveToFile(StrToByteArray(OutputStr), tmpOutFileName.Replace("""", String.Empty))) Then
                            Log(ClientIP & " < FAILED Copying output from process to file [" + tmpOutFileName + "] and placing in debug folder")
                        End If
                    End If
                    If Not (GetClientByIP(ClientIP) Is Nothing) Then
                        Log(ClientIP & " < Copying output from process to client " + ClientIP)

                        If Not SendMessageToClient(ClientIP, StrToByteArray(OutputStr)) Then
                            Try
                                Log(ClientIP & " < FAILED Copying output from process to client " + ClientIP)
                                DisconnectClient(ClientIP)
                            Catch ex2 As Exception
                                'Log(ClientIP & " < Cannot disconnect following process termination " & tmpFileName & ":" & ex2.Message)
                            End Try
                        Else
                            If AutoCloseConnection Then
                                DisconnectClient(ClientIP)
                                Log(ClientIP & " < Auto closing connection to client " + ClientIP)
                            End If
                        End If
                    Else
                        Log(ClientIP & " < Client socket connection [" + ClientIP + "] abandoned prior to returned data stream back to client")
                    End If


                Catch ex As Exception

                End Try

            End If
        Else
            Try
                If DebugFolder <> String.Empty Then
                    If Not (Directory.Exists(DebugFolder)) Then
                        Directory.CreateDirectory(DebugFolder)
                    End If
                    Log(ClientIP & " < Copying output file [" + OutputStr + "] to debug folder")
                    File.Copy(OutputStr, OutputStr.Replace(TempFolder, DebugFolder))
                End If
                If Not (GetClientByIP(ClientIP) Is Nothing) Then
                    Log(ClientIP & " < Streaming output file [" + OutputStr + "] to client")
                    If Not (StreamFileToClient(ClientIP, OutputStr)) Then
                        Try
                            Log(ClientIP & " < FAILED streaming output file [" + OutputStr + "] to client within StreamFileToClient")
                            DisconnectClient(ClientIP)
                        Catch ex2 As Exception
                            'Log(ClientIP & " < Cannot disconnect following process termination " & tmpFileName & ":" & ex2.Message)
                        End Try
                    Else
                        If AutoCloseConnection Then
                            DisconnectClient(ClientIP)
                            Log(ClientIP & " < Auto closing connection to client " + ClientIP)
                        End If
                    End If
                Else
                    Log(ClientIP & " < Client socket connection [" + ClientIP + "] abandoned prior to file stream of file [" + OutputStr + "]")
                    If File.Exists(OutputStr) Then
                        Try
                            File.Delete(OutputStr)
                        Catch ex As Exception

                        End Try
                    End If
                End If

            Catch ex As Exception
                Log(ClientIP & " < FAILED streaming output file [" + OutputStr + "] to client because " + ex.Message)
            End Try

        End If

        '
        If (sender.ExitCode <> 0) Then
            Log(ClientIP & " < Error returned from remote")
            Try
                If DoNotCloseConnectionOnExternalProcFailure = False Then
                    ' Close the connection on failure
                    DisconnectClient(ClientIP)
                Else
                    ' Do not close the connection on failure
                    If EchoErrorMessageOnFail <> String.Empty Then
                        If File.Exists(EchoErrorMessageOnFail) Then
                            'EchoErrorMessageOnFail is a file
                            If Not (StreamFileToClient(ClientIP, EchoErrorMessageOnFail)) Then
                                DisconnectClient(ClientIP)
                            End If
                        Else
                            'EchoErrorMessageOnFail is a literal
                            If Not SendMessageToClient(ClientIP, StrToByteArray(EchoErrorMessageOnFail)) Then
                                DisconnectClient(ClientIP)
                            End If
                        End If

                    End If
                End If
            Catch ex2 As Exception
                'Log(ClientIP & " < Cannot disconnect following process termination " & tmpFileName & ":" & ex2.Message)
            End Try
        Else
            Log(ClientIP & " < Delete Data File")
            If File.Exists(tmpFileName) Then
                File.Delete(tmpFileName)
            End If
            If File.Exists(tmpFileName.Replace(".request", ".close")) Then
                Try
                    DisconnectClient(ClientIP)
                Catch ex2 As Exception
                    'Log(ClientIP & " < Cannot disconnect following process termination " & tmpFileName & ":" & ex2.Message)
                End Try
                File.Delete(tmpFileName.Replace(".request", ".close"))
            End If
        End If
        Log(ClientIP & " < Incoming Data Processing Complete")
    End Sub

    Private Sub ProcessNewData(ByVal ClientIP As String, ByVal DataBytes As Byte(), ByVal PacketSerialNumber As Integer) Handles datain.NewDataArrived
        Try
            If DataBytes.Length <= 0 Then
                Exit Sub
            End If
            If OnReceiveAction.Trim <> String.Empty Then
                If System.IO.File.Exists(OnReceiveAction) Then
                    Log(ClientIP & " > Processing Incoming Data")
                    Dim MyProcess As New System.Diagnostics.Process
                    Dim OutputStr As String = String.Empty
                    Dim OutputBytes As Byte() = Nothing
                    Dim ErrorStr As String = String.Empty
                    Dim IsFile As Boolean = False
                    Dim tmpFileName As String = System.IO.Path.Combine(TempFolder, ClientIP.Replace(":", "-") + ".[" + PacketSerialNumber.ToString.Trim + "].request")
                    Log(ClientIP & " > Saving Incoming Data to a File")
                    If AlwaysPassClientInputAsParameters = False Then
                        If SaveToFile(DataBytes, tmpFileName) Then
                            If DebugFolder <> String.Empty Then
                                If Not (Directory.Exists(DebugFolder)) Then
                                    Directory.CreateDirectory(DebugFolder)
                                End If
                                File.Copy(tmpFileName, tmpFileName.Replace(TempFolder, DebugFolder))
                            End If
                            MyProcess.StartInfo.CreateNoWindow = True
                            MyProcess.StartInfo.RedirectStandardOutput = True
                            MyProcess.StartInfo.RedirectStandardError = True
                            MyProcess.StartInfo.FileName = OnReceiveAction
                            MyProcess.StartInfo.Arguments = """" & tmpFileName & """"
                            MyProcess.StartInfo.UseShellExecute = False
                            Log(ClientIP & " > Starting External Process")
                            MyProcess.EnableRaisingEvents = True
                            AddHandler MyProcess.Exited, AddressOf Me.datain_ProcessExited
                            MyProcess.Start()
                            AllTCPClientProcesses.Add(MyProcess.Id.ToString, ClientIP)

                        Else
                            '
                            '  Error saving file
                            '
                            Log(ClientIP & " > Cannot Save Incoming Data to a File > " & tmpFileName)
                            DisconnectClient(ClientIP)
                        End If
                    Else
                        tmpFileName = System.IO.Path.Combine(TempFolder, ClientIP.Replace(":", "-") + ".[" + PacketSerialNumber.ToString.Trim + "].requestconsole")
                        If DebugFolder <> String.Empty Then
                            If Not (Directory.Exists(DebugFolder)) Then
                                Directory.CreateDirectory(DebugFolder)
                            End If
                            If SaveToFile(DataBytes, tmpFileName.Replace(TempFolder, DebugFolder)) Then

                            End If
                        End If
                        MyProcess.StartInfo.CreateNoWindow = True
                        MyProcess.StartInfo.RedirectStandardOutput = True
                        MyProcess.StartInfo.RedirectStandardError = True
                        MyProcess.StartInfo.FileName = OnReceiveAction
                        MyProcess.StartInfo.Arguments = """" & ByteArrayToStr(DataBytes) & """"
                        MyProcess.StartInfo.UseShellExecute = False
                        Log(ClientIP & " > Starting External Process")
                        MyProcess.EnableRaisingEvents = True
                        AddHandler MyProcess.Exited, AddressOf Me.datain_ProcessExited
                        MyProcess.Start()
                        AllTCPClientProcesses.Add(MyProcess.Id.ToString, ClientIP)
                    End If

                End If
            Else
                Log(ClientIP & " > Ignored data")
            End If
        Catch ex As Exception
            Try
                Log(ClientIP & " > Cannot Save Incoming Data to a File for Client")
                DisconnectClient(ClientIP)
            Catch ex2 As Exception
                'Swallow exception
            End Try
        End Try
    End Sub

    Public Sub Exit_Server()
        localServer.mainStopListen()
        BeginServer.Abort()
    End Sub

    Public Function GetIPFromDNS(ByVal DNSName As String) As String
        Dim strMachineName As String = DNSName
        Dim locIP_toStart As String = "127.0.0.1"
        Dim RequestedIP As String = String.Empty
        Try
            If ListenAddress.ToString <> String.Empty Then
                RequestedIP = ListenAddress.ToString
            End If
        Catch ex As Exception

        End Try

        Dim MyIPAddress As System.Net.IPAddress
        'Get the Host by Name
        Dim ipHost As IPHostEntry
        Dim FoundIP As Boolean = False
        ipHost = Dns.GetHostEntry(strMachineName)
        For Each MyIPAddress In ipHost.AddressList
            If MyIPAddress.ToString.IndexOf(".") > -1 Then
                If MyIPAddress.ToString = RequestedIP Then
                    FoundIP = True
                    locIP_toStart = MyIPAddress.ToString
                    Exit For
                End If
            End If
        Next
        If Not FoundIP Then
            For Each MyIPAddress In ipHost.AddressList
                If (MyIPAddress.ToString.IndexOf(".") > -1) And (MyIPAddress.ToString.IndexOf(":") = -1) Then
                    FoundIP = True
                    locIP_toStart = MyIPAddress.ToString
                    Exit For
                End If
            Next
        End If
        GetIPFromDNS = locIP_toStart
    End Function

    Public Function GetLocalIP() As String
        Dim strMachineName As String = Dns.GetHostName()
        Dim locIP_toStart As String = "127.0.0.1"
        Dim RequestedIP As String = String.Empty
        Try
            If ListenAddress.ToString <> String.Empty Then
                RequestedIP = ListenAddress.ToString
            End If
        Catch ex As Exception

        End Try

        Dim MyIPAddress As System.Net.IPAddress
        'Get the Host by Name
        Dim ipHost As IPHostEntry
        Dim FoundIP As Boolean = False
        ipHost = Dns.GetHostEntry(strMachineName)
        For Each MyIPAddress In ipHost.AddressList
            If MyIPAddress.ToString.IndexOf(".") > -1 Then
                If MyIPAddress.ToString = RequestedIP Then
                    FoundIP = True
                    locIP_toStart = MyIPAddress.ToString
                    Exit For
                End If
            End If
        Next
        If Not FoundIP Then
            For Each MyIPAddress In ipHost.AddressList
                If (MyIPAddress.ToString.IndexOf(".") > -1) And (MyIPAddress.ToString.IndexOf(":") = -1) Then
                    FoundIP = True
                    locIP_toStart = MyIPAddress.ToString
                    Exit For
                End If
            Next
        End If
        GetLocalIP = locIP_toStart
    End Function
    Sub ServerBegin()
        localServer = New TCPServerListener
        Try
            localServer.mainListen(GetLocalIP, Integer.Parse(ListenPort), IsSSL, SSLCertificateFile, TimeOutValue, StringRequiredToSendPackets, ExcludeRequiredPacketsFromTransmission, MaxWorkerThreads)
        Catch ex As Exception
            'Unable to listen?  Go figure.
        End Try

    End Sub

    Private Sub Log(ByVal str As String)    ' Just Display received data
        SyncLock syncfield
            Console.WriteLine(DateTime.Now.ToString("[yyyy/MM/dd HH:mm:ss.fffffff] ") & str)
        End SyncLock
    End Sub

End Class






